package cn.ddiancan.gateway.filter;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import cn.ddiancan.gateway.config.FilterOrdered;
import cn.ddiancan.gateway.config.SsoConstants;
import cn.ddiancan.gateway.service.AuthenticationService;
import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public class AccessFilter implements WebFilter, Ordered {

    @Override
    public int getOrder() {
        return FilterOrdered.ACCESS_FILTER_ORDER;
    }

    @Autowired
    private AuthenticationService authenService;

    private final WhitePathPattern whitePathPattern;

    public AccessFilter(List<String> whiteUrlList) {
        this.whitePathPattern = new WhitePathPattern(whiteUrlList);

    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        return urlWhiteList(exchange).flatMap(
            isWhitelisted -> isWhitelisted ? chain.filter(exchange) : handleLogin(exchange, chain));
    }

    private Mono<Void> handleLogin(ServerWebExchange exchange, WebFilterChain chain) {
        return authenService.doLogin(exchange).flatMap(isAuthenticated -> isAuthenticated ? chain.filter(
            exchange.mutate().request(requestBuilder -> requestBuilder.headers(
                cusumerHeader -> exchange.getAttributes().forEach((key, value) -> {
                    if (key.equalsIgnoreCase(SsoConstants.JWT_TOKEN) || key.equalsIgnoreCase(SsoConstants.UATH_TOKEN)) {
                        cusumerHeader.add(key, value.toString());
                        exchange.getAttributes().remove(key);
                    }
                }))).build()) : authenFailMessage(exchange.getResponse()));
    }

    private Mono<Void> authenFailMessage(ServerHttpResponse response) {
        response.setStatusCode(HttpStatus.FORBIDDEN);
        response.getHeaders().set(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate");
        response.getHeaders().set(HttpHeaders.PRAGMA, "no-cache");
        response.getHeaders().set(HttpHeaders.EXPIRES, "0");
        response.getHeaders().set(HttpHeaders.LAST_MODIFIED, "0");
        Map<String, Object> resultInfo = new HashMap<>();
        resultInfo.put("status", HttpStatus.FORBIDDEN.value());
        resultInfo.put("message", "Access Denied by gateway,any question,Please contact the system manager");
        DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONBytes(resultInfo));
        return response.writeWith(Flux.just(dataBuffer));
    }

    private Mono<Boolean> urlWhiteList(ServerWebExchange exchange) {
        HttpRequest request = exchange.getRequest();
        String rawPath = request.getURI().getRawPath();
        return Mono.just(whitePathPattern.match(rawPath));
    }

    static class WhitePathPattern {

        private final AntPathMatcher antPathMatcher = new AntPathMatcher();

        List<String> whitePathList = new ArrayList<>();

        public WhitePathPattern(List<String> whiteUrlList) {
            whitePathList.add("/**/actuator/**");
            whitePathList.add("/**/auth/**");
            whitePathList.add("/**/swagger-resources/**");
            whitePathList.add("/**/v3/api-docs");
            whitePathList.add("/**/v3/api-docs/**");
            whitePathList.add("/**/*.html");
            whitePathList.add("/**/*.ico");
            if (!CollectionUtils.isEmpty(whiteUrlList)) {
                this.whitePathList.addAll(whiteUrlList);
            }
        }

        public boolean match(String url) {
            return this.whitePathList.stream().anyMatch(str -> antPathMatcher.match(str, url));
        }
    }

}
